//
// Created by vivi on 09/02/2018.
//

#pragma once

#include <string>
#include <cstdio>
#include <algorithm>
#include <array>
#include "core/collider/collider.h"

namespace HairEngine {

	class SignedDistanceFieldCollisionObject;

	/*
	 * The data store in the SDF Collider vertex
	 */
	struct SDFColliderVertex {

		typedef SDFColliderVertex *Ptr;

		float distance; //the distance to the surface of the SDFCollider
	};

	/*
	 * A cell is represented by 8 surrounding vertexes
	 */
	struct SDFColliderCell {
		typedef SDFColliderCell *Ptr;

		/*
		 * The pointers are ordered by:
		 *     v[0] ---> The lowest vertex in the cell
		 *     v[1] ---> v[0] move along z axis
		 *     v[2] ---> v[0] move along y axis
		 *     v[3] ---> v[0] move along y and z axis
		 *     v[4] ---> v[0] move along x axis
		 *     v[5] ---> v[4] move along z axis
		 *     v[6] ---> v[4] move along y axis
		 *     v[7] ---> v[4] move along y and z axis
		 * In short, if we see the index in binary format [ax, ay, az], when a? = 0 means not moving in that axis from v[0]
		 * , otherwise move in that axis
		 *
		 */
		std::array<SDFColliderVertex::Ptr, 8> v;

		SDFColliderCell() = default;
		SDFColliderCell(const SDFColliderCell &other);
	};

	/*
	 * SDFCollider (signed distance field collider) is a collider which the distance information
	 * has been precomputed in a grid based voxel lattice. It supports a constant time distance and gradient
	 * query method. The SDFCollider can be initialized from a storage file .sdf (binary) and .sdf2 (binary)
	 *
	 * The .sdf, .sdf2 file format:
	 *     <nx> <ny> <nz> ===> [int32_t]
	 *     <originX> <originY> <originZ> ===> [float]
	 *     <dx> for sdf (or <dx> <dy> <dz> for .sdf2) ===> [float]
	 *     <value1> <value2> <value3> ... ===> [float]
     *
	 *	ni, nj, nk are the integers that tells how many vertexes in the lattice of x, y and z coordinates,
	 *  originX, originY, originZ tells the start origin (which is the lowest point), dx, dy, dz tells the size of
	 *  cell(8 nearby vertex forms a cell) in x, y, z coordinates.
	 *  And value1... stores the distance value of the each points
	 */
	class SDFCollider: public Collider {

		friend class SignedDistanceFieldCollisionObject;

	Public:
		using Cell = SDFColliderCell;
		using Vertex = SDFColliderVertex;

		constexpr static const float SDF_INFINITY = 1e30f;

	Public:
		//it could be initialized with .sdf file format and .sdf2 file format
		explicit SDFCollider(const std::string & filePath);

		~SDFCollider() {
			if (vertexes)
				delete [] vertexes;
		}

		float distance(const Eigen::Vector3f & pos, Eigen::Vector3f * outGradientPtr = nullptr) const override;

		/*
		 * For debugging, it will search all cells and checks whether this cell contains a zero contour plane,
		 * if so, it will write out the center point of this cell to a file. And also we write the bounding box to
		 * the file.
		 *
		 * The file format is .vply, which contains:
		 *     wireframebox <minx> <miny> <minz> <maxx> <maxy> <maxz> //the bounding wireframe
		 *     p <x> <y> <z> //the point
		 *     l <x> <y> <z> <x + normal_x> <y + normal_y> <z + normal_z>
		 *     ...
		 *
		 * The return value indicates whether those information has successfully written to file
		 */
		bool debugWriteZeroIsoContourToFile(const std::string &filePath);

	Protected:
		/*
		 * File data storage
		 *
		 * nx, ny, nz: The vertex count in the x, y, z coordinates, the total count of all vertexes is nx * ny * nz;
		 * minP, maxP: The minimum bounding point and the maximum bounding point
		 * vertexCount: The size of vertexes array
		 * vertexes: A array, size equals to nx * ny * nz, storage the data information of each vertex, in the ascending order
		 *            of x, y, z
		 * cellSize: The size of a cell in x, y, z coordinates
		 * cellSizeReciprocal: Equals to 1.0f / cellSize, in each coordinate, for fast computation since division
		 * in computer is much slower than multiplication
		 */
		size_t nx, ny, nz;
		Eigen::Vector3f minP;
		Eigen::Vector3f maxP;
		Eigen::AlignedBox3f bounding;
		Eigen::Vector3f cellSize;
		Eigen::Vector3f cellSizeReciprocal; //Equals to "1.0f / cellSize", for better computation

		SDFColliderVertex *vertexes = nullptr;
		size_t vertexCount;

		size_t nyz; //Equals to ny * nz, for better index querying

		/*
		 * Get the surrounding cell for a given point pos, it will not check whether the point in the bounding box for
		 * speed, outT = (tx, ty, tz) is the remainder, which means:
		 *     pos = the-lowest-point-position-in-the-cell + outT * cellSize
		 * Thus, 0.0f < tx, ty, tz < 1.0f
		 */
		Cell getCell(const Eigen::Vector3f & pos, Eigen::Vector3f *outT = nullptr) const;
	};
}
